package com.open.rbac.service.sys.impl;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.open.center.common.constant.OpenField;
import com.open.center.common.exception.OpenException;
import com.open.center.common.util.CopyUtils;
import com.open.rbac.constant.BooleanType;
import com.open.rbac.constant.ImmutableRole;
import com.open.rbac.model.dto.sys.RoleDto;
import com.open.rbac.model.dto.sys.RoleUserRelDto;
import com.open.rbac.model.entity.sys.Role;
import com.open.rbac.model.vo.sys.RoleMenuRelVo;
import com.open.rbac.model.vo.sys.RoleVo;
import com.open.rbac.service.sys.IRoleMenuRelService;
import com.open.rbac.service.sys.IRoleService;
import com.open.rbac.service.sys.IRoleUserRelService;
import com.open.rbac.service.sys.repository.RoleRepository;


/**
 * 角色
 *
 * @author Riche's
 * @since 2022/8/24
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RoleServiceImpl implements IRoleService {

    private final RoleRepository roleRepository;

    private final IRoleUserRelService roleUserRelService;

    private final IRoleMenuRelService roleMenuRelService;


    @Override
    public RoleDto create(RoleVo roleVo) {
        Role role = CopyUtils.copy(roleVo, Role.class);
        Optional<Role> optional = roleRepository.findById(role.getId());
        optional.ifPresent(o -> {
            throw new OpenException(String.format("角色ID[%s, %s]已存在", o.getId(), o.getName()));
        });
        role.setImmutable(BooleanType.NO);
        roleRepository.save(role);
        return CopyUtils.copy(role, RoleDto.class);
    }

    @Override
    public RoleDto update(RoleVo roleVo) {
        for (ImmutableRole mu : ImmutableRole.values()) {
            if (mu.value().equals(roleVo.getId())) {
                log.error("{} 为系统内置角色不可更改", roleVo.getName());
                throw new OpenException("内置角色不能修改");
            }
        }
        Optional<Role> optional = roleRepository.findById(roleVo.getId());
        return optional.map(r -> {
            CopyUtils.copyNonNull(roleVo, r);
            r.setImmutable(BooleanType.NO);
            r.setRemark(roleVo.getRemark());
            roleRepository.save(r);
            return CopyUtils.copy(r, RoleDto.class);
        }).orElse(null);
    }

    @Override
    public RoleDto findById(String id) {
        Optional<Role> optional = roleRepository.findById(id);
        return optional.map(role -> CopyUtils.copy(role, RoleDto.class)).orElse(null);
    }

    @Transactional
    @Override
    public void deleteById(String id) {
        for (ImmutableRole mu : ImmutableRole.values()) {
            if (mu.value().equals(id)) {
                log.error("{} 为系统内置角色不能删除", id);
                throw new OpenException("内置角色不能删除");
            }
        }
        roleUserRelService.deleteReallyByRoleId(id);
        roleMenuRelService.deleteReallyByRoleId(id);
        roleRepository.deleteReallyById(id);
    }

    @Override
    public void enable(String id) {
        roleRepository.updateEnable(id, true);
    }

    @Override
    public void disable(String id) {
        for (ImmutableRole mu : ImmutableRole.values()) {
            if (id.equals(mu.value())) {
                log.error("{} 为系统内置角色不能禁用", mu.value());
                throw new OpenException("内置角色不能禁用");
            }
        }
        roleRepository.updateEnable(id, false);
    }

    @Override
    public List<RoleDto> listByName(String name) {
        return CopyUtils.listCopy(roleRepository.findByName(name), RoleDto.class);
    }

    @Override
    public List<RoleDto> list(RoleVo roleVo) {
        List<Role> roles = roleRepository.findAll(example(roleVo),
            Sort.by(Sort.Direction.DESC, OpenField.IMMUTABLE.getId())
                .and(Sort.by(Sort.Direction.DESC, OpenField.CREATE_TIME.getId())));
        return CopyUtils.listCopy(roles, RoleDto.class);
    }

    public Example<Role> example(RoleVo roleVo) {
        Role role = new Role();
        if (roleVo != null) {
            role.setId(roleVo.getId());
            role.setName(roleVo.getName());
            role.setImmutable(roleVo.getImmutable());
            role.setRemark(roleVo.getRemark());
            role.setEnable(roleVo.getEnable());
        }
        ExampleMatcher matcher = ExampleMatcher.matching()
            .withMatcher("id", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("name", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("remark", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("enable", ExampleMatcher.GenericPropertyMatcher::exact)
            .withIgnoreCase(true);
        return Example.of(role, matcher);
    }


    @Override
    public Map<String, List<RoleDto>> mapByUserIds(List<String> userIds) {
        List<RoleUserRelDto> relDtos = roleUserRelService.listByUserIds(userIds);
        if (CollectionUtils.isEmpty(relDtos)) {
            return Map.of();
        }
        List<String> ids = relDtos.stream().map(RoleUserRelDto::getRoleId).distinct().toList();
        List<RoleDto> roleDtos = CopyUtils.listCopy(roleRepository.findByIds(ids), RoleDto.class);
        Map<String, RoleDto> mapRoleDto = roleDtos.stream().collect(Collectors.toMap(RoleDto::getId, Function.identity()));
        return relDtos.stream().collect(Collectors.groupingBy(
            RoleUserRelDto::getUserId,
            Collectors.mapping(rel -> mapRoleDto.get(rel.getRoleId()),
                Collectors.filtering(Objects::nonNull, Collectors.toList())
            )
        ));
    }


    @Transactional
    @Override
    public void createMenuRels(String roleId, List<String> menuIds) {
        if (CollectionUtils.isEmpty(menuIds)) {
            throw new OpenException("菜单列表为必选项");
        }
        roleMenuRelService.deleteReallyByRoleId(roleId);
        List<RoleMenuRelVo> rels = menuIds.stream()
            .map(menuId -> RoleMenuRelVo.of(roleId, menuId)).toList();
        roleMenuRelService.create(rels);
    }
}
